package com.cqcomponents.utils;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import javax.jcr.Item;
import javax.jcr.Node;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cqcomponents.api.components.IComponent;
import com.cqcomponents.api.exceptions.ComponentNotFoundException;

public final class ComponentUtils {
	protected static final Logger logger = LoggerFactory
			.getLogger(ComponentUtils.class);

	public static final String NOT_FIRST_COMPONENT_ATTRIBUTE_NAME = "CQC:FIRST_COMPONENT";

	public static final String getMethodNameWithoutSet(Method method) {
		String result = StringUtils.replaceOnce(method.getName(), "set", "");
		result = result.toLowerCase().charAt(0) + result.substring(1);

		return result;
	}

	public static Set<String> buildListOfPathsToRemove(
			ResourceResolver resourceResolver, Set<String> paths) {
		Set<String> result = new HashSet<String>();
		for (String path : paths) {
			if (result.contains(path)) {
				continue;
			}
			while (path.contains("/")) {
				if (StringUtils.endsWith(path, "/jcr:content")) {
					// Stop at jcr:content node
					break;
				}
				Resource resource = resourceResolver.getResource(path);
				if (resource != null) {
					if (ComponentUtils.isNode(resource)) {
						result.add(resource.getPath());
					} else {
						// This is not a node, probably a property --> Add
						// parent if it is a node otherwise ignore
						Resource parentResource = resource.getParent();
						if (ComponentUtils.isNode(parentResource)) {
							result.add(parentResource.getPath());
						}
					}
				}
				path = StringUtils.substringBeforeLast(path, "/");
			}
		}

		return result;
	}

	/**
	 * Checks if caching is disabled for this resource/component. Caching is
	 * disabled when the disableCaching flag is set on the component definition
	 * or the component bean method getHasContent() returns false.
	 * 
	 * @param resource
	 * @param component
	 * @return
	 */
	public static boolean isCachingDisabled(Resource resource,
			IComponent component) {

		String resourceTypePath = resource.getResourceType();
		Resource resourceTypeResource = resource.getResourceResolver()
				.getResource(resourceTypePath);
		boolean result = ResourceUtil.getValueMap(resourceTypeResource).get(
				"disableCaching", false);

		return result || !component.getHasContent();
	}

	public static Resource getPageResource(Resource resource) {
		if (resource != null && !isPageComponent(resource)) {
			return getPageResource(resource.getParent());
		}
		if (resource != null && resource.getPath().endsWith("jcr:content")) {
			return resource.getParent();
		}
		return resource;
	}

	public static boolean isPageComponent(Resource resource) {
		Resource page = resource;
		if (!page.getPath().endsWith("jcr:content")) {
			page = page.getChild("jcr:content");
		}
		return ResourceUtil.isA(page, "cqcomponents/components/page")
				|| ResourceUtil.isA(page, "/apps/cqcomponents/components/page");
	}

	public static boolean isContentReferenceComponent(Resource resource) {
		return ResourceUtil.isA(resource,
				"cqcomponents/components/contentreference")
				|| ResourceUtil.isA(resource,
						"/apps/cqcomponents/components/contentreference");
	}

	@SuppressWarnings("unchecked")
	public static final Class<IComponent> resourceToComponentClass(
			Resource resource) throws ComponentNotFoundException {

		Class<IComponent> result = null;

		String resourceType = resource.getResourceType();
		Resource resourceTypeResource = resource.getResourceResolver()
				.getResource(resourceType);

		while (resourceTypeResource != null && result == null) {
			if (StringUtils.startsWith(resourceType, "/apps/")) {
				resourceType = resourceType.substring(6);
			}

			String[] splits = resourceType.split("/");

			StringBuffer className = new StringBuffer();
			className.append("com");

			for (int i = 0; i < splits.length - 1; i++) {
				className.append(".");
				className.append(splits[i].toLowerCase().trim());
			}
			String last = splits[splits.length - 1].trim();
			className.append(".");
			className.append(last.substring(0, 1).toUpperCase()
					+ last.substring(1));

			logger.debug("Computed class name: " + className.toString()
					+ " for resource type: " + resourceType);

			try {
				result = (Class<IComponent>) Class
						.forName(className.toString());
			} catch (ClassNotFoundException cce) {
				logger.warn(
						"Found resourceType {} resolves to class {} which was not found.",
						new Object[] { resourceType, className.toString() });
			}

			if (result == null) {
				resourceType = ResourceUtil.getResourceSuperType(
						resource.getResourceResolver(), resourceType);
				resourceTypeResource = resource.getResourceResolver()
						.getResource(resourceType);
			}
		}

		if (result == null) {
			throw new ComponentNotFoundException(resource);
		}

		return result;
	}

	public static boolean isCQCComponent(Resource resource) {
		return ResourceUtil.isA(resource, "cqcomponents/components/base")
				|| ResourceUtil.isA(resource,
						"/apps/cqcomponents/components/base");
	}

	public static boolean isNode(Resource resource) {
		Item item = resource != null ? resource.adaptTo(Node.class) : null;
		return item != null;
	}

	public static boolean isFirstComponent(HttpServletRequest httpServletRequest) {
		if (httpServletRequest.getAttribute(NOT_FIRST_COMPONENT_ATTRIBUTE_NAME) == null) {
			// This is the first component rendered
			httpServletRequest.setAttribute(NOT_FIRST_COMPONENT_ATTRIBUTE_NAME,
					"1");
			return true;
		} else {
			return false;
		}
	}

}
